package com.gframework.util.encry;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.interfaces.RSAKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.Base64.Encoder;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;

/**
 * RSA加密解密工具类.
 * 不受到长度限制，但不是建议加密较长得内容。
 * <p>
 * 支持公钥加密私钥解密，和私钥加密公钥解密。你也可以只有公钥或私钥其中得一项，然后只进行加密或解密操作。
 * 每次加密或解密大约会占用 (keySize * 5 / 512)MB 的内存
 * </p>
 * <p>
 * 本类及相关RSA操作类均是线程安全的
 * </p>
 * 
 * @since 1.0.0
 * @author Ghwolf
 * @see RSAKeyPair
 * @see org.apache.commons.codec.DigestUtils
 * @see EncryptUtils
 */
public class RSAUtils {

	/**
	 * 默认字符串编码
	 */
	private static final String DEFAULT_CHARSET = "UTF-8";
	/**
	 * 加密算法
	 */
	private static final String ALGORITHM = "RSA";
	/**
	 * 默认keysize长度：{@value}
	 */
	private static final int DEFAULT_KEYSIZE = 1024;

	/**
	 * 用于封装密钥对的操作对象.<br>
	 * 本类主要是为了扩展{@link KeyPair}不能直接获取字符串形式的密钥信息的缺陷。
	 * 可以直接通过本类取得经过base64转码的公钥和私钥字符串。
	 * 
	 * @since 1.0.0
	 * @author Ghwolf
	 */
	public static class RSAKeyPair {

		/**
		 * 密钥对对象
		 */
		private KeyPair keyPair;
		/**
		 * 公钥字符串
		 */
		private String publicKeyString;
		/**
		 * 私钥字符串
		 */
		private String privateKeyString;

		RSAKeyPair(KeyPair keyPair) {
			this.keyPair = keyPair ;
			Encoder encoder = Base64.getEncoder();
			this.publicKeyString = encoder.encodeToString(keyPair.getPublic().getEncoded());
			this.privateKeyString = encoder.encodeToString(keyPair.getPrivate().getEncoded());
		}

		/**
		 * 取得公钥字符串
		 * 
		 * @return 公钥字符串
		 */
		public String getPublicKeyString() {
			return this.publicKeyString;
		}

		/**
		 * 取得私钥字符串
		 * 
		 * @return 私钥字符串
		 */
		public String getPrivateKeyString() {
			return this.privateKeyString;
		}

		/**
		 * 取得公钥对象
		 * 
		 * @return 公钥对象
		 */
		public RSAPublicKey getPublicKey() {
			return (RSAPublicKey)this.keyPair.getPublic();
		}

		/**
		 * 取得私钥对象
		 * 
		 * @return 私钥对象
		 */
		public RSAPrivateKey getPrivateKey() {
			return (RSAPrivateKey)this.keyPair.getPrivate();
		}
		
		@Override
		public String toString() {
			return "rsa publicKey: " + this.publicKeyString + "\n" + 
					"rsa privateKey: " + this.privateKeyString + "\n";
		}

	}
	
	private RSAUtils(){}

	/**
	 * 生成一个keySize长度为{@link #DEFAULT_KEYSIZE}的密钥对
	 * 
	 * @return 返回密钥对封装对象
	 * @throws RuntimeException
	 *             所有Exception异常变为RuntimeException抛出，通过{@link RuntimeException#getCause()}取得原始异常
	 * 
	 *             <pre>
	 * · {@link NoSuchAlgorithmException} 加密类型在当前环境不可用。通常可以忽略此异常
	 *             </pre>
	 */
	public static RSAKeyPair createKeys() {
		return createKeys(DEFAULT_KEYSIZE);
	}

	/**
	 * 本方法负责生成一个新的密钥对.
	 * 
	 * @param keySize 加密长度，最低是512，建议是512的倍数，目前主流长度是1024
	 * @return 返回密钥对封装对象
	 * @throws RuntimeException
	 *             所有Exception异常变为RuntimeException抛出，通过{@link RuntimeException#getCause()}取得原始异常
	 * 
	 *             <pre>
	 * · {@link NoSuchAlgorithmException} 加密类型在当前环境不可用。通常可以忽略此异常
	 *             </pre>
	 */
	public static RSAKeyPair createKeys(int keySize) {
		// 为RSA算法创建一个KeyPairGenerator对象
		KeyPairGenerator kpg;
		try {
			kpg = KeyPairGenerator.getInstance(ALGORITHM);
		} catch (NoSuchAlgorithmException e) {
			// Ignore
			throw new RuntimeException("不支持" + ALGORITHM + "加密算法！", e);
		}

		// 初始化KeyPairGenerator对象,密钥长度
		kpg.initialize(keySize);
		// 生成密匙对
		KeyPair keyPair = kpg.generateKeyPair();
		return new RSAKeyPair(keyPair);
	}

	/**
	 * 转换RSA公钥对象.
	 * 将经过base64转码过的公钥字符串转换为RSA公钥对象。
	 * 
	 * @param publicKey 经过base64转码的公钥字符串
	 * @return 返回公钥对象
	 * @throws RuntimeException
	 *             所有Exception异常变为RuntimeException抛出，通过{@link RuntimeException#getCause()}取得原始异常
	 * 
	 *             <pre>
	 * · {@link InvalidKeySpecException} 公钥信息解析错误
	 * · {@link NoSuchAlgorithmException} 加密类型在当前环境不可用。通常可以忽略此异常
	 *             </pre>
	 */
	public static RSAPublicKey getPublicKey(String publicKey) {
		try {
			// 通过X509编码的Key指令获得公钥对象
			KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
			X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(Base64.getDecoder().decode(publicKey));
			RSAPublicKey key = (RSAPublicKey) keyFactory.generatePublic(x509KeySpec);
			return key;
		} catch (NoSuchAlgorithmException e) {
			// Ignore
			throw new RuntimeException("不支持" + ALGORITHM + "加密算法！", e);
		} catch (InvalidKeySpecException e) {
			throw new RuntimeException("公钥不正确：" + publicKey, e);
		}
	}

	/**
	 * 转换RSA私钥对象.
	 * 将经过base64转码过的私钥字符串转换为RSA私钥对象。
	 * 
	 * @param privateKey 经过base64转码的私钥字符串
	 * @return 返回私钥对象
	 * @throws RuntimeException
	 *             所有Exception异常变为RuntimeException抛出，通过{@link RuntimeException#getCause()}取得原始异常
	 * 
	 *             <pre>
	 * · {@link InvalidKeySpecException} 私钥信息解析错误
	 * · {@link NoSuchAlgorithmException} 加密类型在当前环境不可用。通常可以忽略此异常
	 *             </pre>
	 */
	public static RSAPrivateKey getPrivateKey(String privateKey) {
		try {
			// 通过PKCS#8编码的Key指令获得私钥对象
			KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
			PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey));
			RSAPrivateKey key = (RSAPrivateKey) keyFactory.generatePrivate(pkcs8KeySpec);
			return key;
		} catch (NoSuchAlgorithmException e) {
			// Ignore
			throw new RuntimeException("不支持" + ALGORITHM + "加密算法！", e);
		} catch (InvalidKeySpecException e) {
			throw new RuntimeException("私钥不正确：" + privateKey, e);
		}
	}

	// ## 加密

	/**
	 * 公钥加密数据，数据长度最好保持在 keySize/8 以下.
	 * 
	 * @param data 要加密的数据
	 * @param publicKey 公钥对象
	 * @return 返回加密后的结果
	 * @throws RuntimeException
	 *             通过{@link RuntimeException#getCause()}方法可以取得实际的异常原因
	 * 
	 *             <pre>
	 * · {@link InvalidKeyException} 这是无效密钥（无效编码、错误长度、未初始化等）
	 * · {@link NoSuchAlgorithmException} 加密类型在当前环境不可用。通常可以忽略此异常
	 * · {@link NoSuchPaddingException} 填充机制在当前环境不可用。通常可以忽略此异常。
	 * · {@link BadPaddingException} 当期望输入数据的特定填充机制但数据未正确填充时，会抛出此异常。通常是因为密钥错误，不能加密或解密此数据
	 * · {@link IllegalBlockSizeException} 当提供给块密码的数据长度不正确时，抛出该异常，即与密码的块大小不匹配。可以忽略此异常，如果出现则为本类自身代码错误。
	 *             </pre>
	 */
	public static byte[] publicEncrypt(byte[] data, RSAPublicKey publicKey) {
		return rsaCodec(data, publicKey, Cipher.ENCRYPT_MODE);
	}

	/**
	 * 公钥加密字符串数据，数据长度最好保持在 keySize/8 以下.
	 * 
	 * @param data 要加密的数据
	 * @param publicKey 公钥对象
	 * @param charset 字符串编码类型
	 * @return 返回加密后的结果
	 * @throws RuntimeException
	 *             通过{@link RuntimeException#getCause()}方法可以取得实际的异常原因
	 * 
	 *             <pre>
	 * · {@link InvalidKeyException} 这是无效密钥（无效编码、错误长度、未初始化等）
	 * · {@link NoSuchAlgorithmException} 加密类型在当前环境不可用。通常可以忽略此异常
	 * · {@link NoSuchPaddingException} 填充机制在当前环境不可用。通常可以忽略此异常。
	 * · {@link BadPaddingException} 当期望输入数据的特定填充机制但数据未正确填充时，会抛出此异常。通常是因为密钥错误，不能加密或解密此数据
	 * · {@link IllegalBlockSizeException} 当提供给块密码的数据长度不正确时，抛出该异常，即与密码的块大小不匹配。可以忽略此异常，如果出现则为本类自身代码错误。
	 * · {@link UnsupportedEncodingException} 编码不存在
	 *             </pre>
	 */
	public static String publicEncrypt(String data, RSAPublicKey publicKey, String charset) {
		return rsaCodec(data, publicKey, Cipher.ENCRYPT_MODE, charset);
	}

	/**
	 * 公钥加密字符串数据（默认{@code UTF-8}编码），数据长度最好保持在 keySize/8 以下.
	 * 
	 * @param data 要加密的数据
	 * @param publicKey 公钥对象
	 * @return 返回加密后的结果
	 * @throws RuntimeException
	 *             通过{@link RuntimeException#getCause()}方法可以取得实际的异常原因
	 * 
	 *             <pre>
	 * · {@link InvalidKeyException} 这是无效密钥（无效编码、错误长度、未初始化等）
	 * · {@link NoSuchAlgorithmException} 加密类型在当前环境不可用。通常可以忽略此异常
	 * · {@link NoSuchPaddingException} 填充机制在当前环境不可用。通常可以忽略此异常。
	 * · {@link BadPaddingException} 当期望输入数据的特定填充机制但数据未正确填充时，会抛出此异常。通常是因为密钥错误，不能加密或解密此数据
	 * · {@link IllegalBlockSizeException} 当提供给块密码的数据长度不正确时，抛出该异常，即与密码的块大小不匹配。可以忽略此异常，如果出现则为本类自身代码错误。
	 *             </pre>
	 */
	public static String publicEncrypt(String data, RSAPublicKey publicKey) {
		return publicEncrypt(data, publicKey, DEFAULT_CHARSET);
	}

	/**
	 * 公钥加密数据，数据长度最好保持在 keySize/8 以下.
	 * 
	 * @param data 要加密的数据
	 * @param publicKey 公钥字符串
	 * @return 返回加密后的结果
	 * @throws RuntimeException
	 *             通过{@link RuntimeException#getCause()}方法可以取得实际的异常原因
	 * 
	 *             <pre>
	 * · {@link InvalidKeyException} 这是无效密钥（无效编码、错误长度、未初始化等）
	 * · {@link NoSuchAlgorithmException} 加密类型在当前环境不可用。通常可以忽略此异常
	 * · {@link NoSuchPaddingException} 填充机制在当前环境不可用。通常可以忽略此异常。
	 * · {@link BadPaddingException} 当期望输入数据的特定填充机制但数据未正确填充时，会抛出此异常。通常是因为密钥错误，不能加密或解密此数据
	 * · {@link IllegalBlockSizeException} 当提供给块密码的数据长度不正确时，抛出该异常，即与密码的块大小不匹配。可以忽略此异常，如果出现则为本类自身代码错误。
	 * · {@link InvalidKeySpecException} 公钥信息解析错误
	 *             </pre>
	 */
	public static byte[] publicEncrypt(byte[] data, String publicKey) {
		return rsaCodec(data, getPublicKey(publicKey), Cipher.ENCRYPT_MODE);
	}

	/**
	 * 公钥加密字符串数据，数据长度最好保持在 keySize/8 以下.
	 * 
	 * @param data 要加密的数据
	 * @param publicKey 公钥字符串
	 * @param charset 字符串编码类型
	 * @return 返回加密后的结果
	 * @throws RuntimeException
	 *             通过{@link RuntimeException#getCause()}方法可以取得实际的异常原因
	 * 
	 *             <pre>
	 * · {@link InvalidKeyException} 这是无效密钥（无效编码、错误长度、未初始化等）
	 * · {@link NoSuchAlgorithmException} 加密类型在当前环境不可用。通常可以忽略此异常
	 * · {@link NoSuchPaddingException} 填充机制在当前环境不可用。通常可以忽略此异常。
	 * · {@link BadPaddingException} 当期望输入数据的特定填充机制但数据未正确填充时，会抛出此异常。通常是因为密钥错误，不能加密或解密此数据
	 * · {@link IllegalBlockSizeException} 当提供给块密码的数据长度不正确时，抛出该异常，即与密码的块大小不匹配。可以忽略此异常，如果出现则为本类自身代码错误。
	 * · {@link UnsupportedEncodingException} 编码不存在
	 * · {@link InvalidKeySpecException} 公钥信息解析错误
	 *             </pre>
	 */
	public static String publicEncrypt(String data, String publicKey, String charset) {
		return rsaCodec(data, getPublicKey(publicKey), Cipher.ENCRYPT_MODE, charset);
	}

	/**
	 * 公钥加密字符串数据（默认{@code UTF-8}编码），数据长度最好保持在 keySize/8 以下.
	 * 
	 * @param data 要加密的数据
	 * @param publicKey 公钥字符串
	 * @return 返回加密后的结果
	 * @throws RuntimeException
	 *             通过{@link RuntimeException#getCause()}方法可以取得实际的异常原因
	 * 
	 *             <pre>
	 * · {@link InvalidKeyException} 这是无效密钥（无效编码、错误长度、未初始化等）
	 * · {@link NoSuchAlgorithmException} 加密类型在当前环境不可用。通常可以忽略此异常
	 * · {@link NoSuchPaddingException} 填充机制在当前环境不可用。通常可以忽略此异常。
	 * · {@link BadPaddingException} 当期望输入数据的特定填充机制但数据未正确填充时，会抛出此异常。通常是因为密钥错误，不能加密或解密此数据
	 * · {@link IllegalBlockSizeException} 当提供给块密码的数据长度不正确时，抛出该异常，即与密码的块大小不匹配。可以忽略此异常，如果出现则为本类自身代码错误。
	 * · {@link InvalidKeySpecException} 公钥信息解析错误
	 *             </pre>
	 */
	public static String publicEncrypt(String data, String publicKey) {
		return publicEncrypt(data, publicKey, DEFAULT_CHARSET);
	}

	/**
	 * 私钥加密数据，数据长度最好保持在 keySize/8 以下.
	 * 
	 * @param data 要加密的数据
	 * @param privateKey 私钥对象
	 * @return 返回加密后的结果
	 * @throws RuntimeException
	 *             通过{@link RuntimeException#getCause()}方法可以取得实际的异常原因
	 * 
	 *             <pre>
	 * · {@link InvalidKeyException} 这是无效密钥（无效编码、错误长度、未初始化等）
	 * · {@link NoSuchAlgorithmException} 加密类型在当前环境不可用。通常可以忽略此异常
	 * · {@link NoSuchPaddingException} 填充机制在当前环境不可用。通常可以忽略此异常。
	 * · {@link BadPaddingException} 当期望输入数据的特定填充机制但数据未正确填充时，会抛出此异常。通常是因为密钥错误，不能加密或解密此数据
	 * · {@link IllegalBlockSizeException} 当提供给块密码的数据长度不正确时，抛出该异常，即与密码的块大小不匹配。可以忽略此异常，如果出现则为本类自身代码错误。
	 *             </pre>
	 */
	public static byte[] privateEncrypt(byte[] data, RSAPrivateKey privateKey) {
		return rsaCodec(data, privateKey, Cipher.ENCRYPT_MODE);
	}

	/**
	 * 私钥加密字符串数据，数据长度最好保持在 keySize/8 以下.
	 * 
	 * @param data 要加密的数据
	 * @param privateKey 私钥对象
	 * @param charset 字符串编码类型
	 * @return 返回加密后的结果
	 * @throws RuntimeException
	 *             通过{@link RuntimeException#getCause()}方法可以取得实际的异常原因
	 * 
	 *             <pre>
	 * · {@link InvalidKeyException} 这是无效密钥（无效编码、错误长度、未初始化等）
	 * · {@link NoSuchAlgorithmException} 加密类型在当前环境不可用。通常可以忽略此异常
	 * · {@link NoSuchPaddingException} 填充机制在当前环境不可用。通常可以忽略此异常。
	 * · {@link BadPaddingException} 当期望输入数据的特定填充机制但数据未正确填充时，会抛出此异常。通常是因为密钥错误，不能加密或解密此数据
	 * · {@link IllegalBlockSizeException} 当提供给块密码的数据长度不正确时，抛出该异常，即与密码的块大小不匹配。可以忽略此异常，如果出现则为本类自身代码错误。
	 * · {@link UnsupportedEncodingException} 编码不存在
	 *             </pre>
	 */
	public static String privateEncrypt(String data, RSAPrivateKey privateKey, String charset) {
		return rsaCodec(data, privateKey, Cipher.ENCRYPT_MODE, charset);
	}

	/**
	 * 私钥加密字符串数据（默认{@code UTF-8}编码），数据长度最好保持在 keySize/8 以下.
	 * 
	 * @param data 要加密的数据
	 * @param privateKey 私钥对象
	 * @return 返回加密后的结果
	 * @throws RuntimeException
	 *             通过{@link RuntimeException#getCause()}方法可以取得实际的异常原因
	 * 
	 *             <pre>
	 * · {@link InvalidKeyException} 这是无效密钥（无效编码、错误长度、未初始化等）
	 * · {@link NoSuchAlgorithmException} 加密类型在当前环境不可用。通常可以忽略此异常
	 * · {@link NoSuchPaddingException} 填充机制在当前环境不可用。通常可以忽略此异常。
	 * · {@link BadPaddingException} 当期望输入数据的特定填充机制但数据未正确填充时，会抛出此异常。通常是因为密钥错误，不能加密或解密此数据
	 * · {@link IllegalBlockSizeException} 当提供给块密码的数据长度不正确时，抛出该异常，即与密码的块大小不匹配。可以忽略此异常，如果出现则为本类自身代码错误。
	 *             </pre>
	 */
	public static String privateEncrypt(String data, RSAPrivateKey privateKey) {
		return privateEncrypt(data, privateKey, DEFAULT_CHARSET);
	}

	/**
	 * 私钥加密数据，数据长度最好保持在 keySize/8 以下.
	 * 
	 * @param data 要加密的数据
	 * @param privateKey 私钥字符串
	 * @return 返回加密后的结果
	 * @throws RuntimeException
	 *             通过{@link RuntimeException#getCause()}方法可以取得实际的异常原因
	 * 
	 *             <pre>
	 * · {@link InvalidKeyException} 这是无效密钥（无效编码、错误长度、未初始化等）
	 * · {@link NoSuchAlgorithmException} 加密类型在当前环境不可用。通常可以忽略此异常
	 * · {@link NoSuchPaddingException} 填充机制在当前环境不可用。通常可以忽略此异常。
	 * · {@link BadPaddingException} 当期望输入数据的特定填充机制但数据未正确填充时，会抛出此异常。通常是因为密钥错误，不能加密或解密此数据
	 * · {@link IllegalBlockSizeException} 当提供给块密码的数据长度不正确时，抛出该异常，即与密码的块大小不匹配。可以忽略此异常，如果出现则为本类自身代码错误。
	 * · {@link InvalidKeySpecException} 私钥信息解析错误
	 *             </pre>
	 */
	public static byte[] privateEncrypt(byte[] data, String privateKey) {
		return rsaCodec(data, getPrivateKey(privateKey), Cipher.ENCRYPT_MODE);
	}

	/**
	 * 私钥加密字符串数据，数据长度最好保持在 keySize/8 以下.
	 * 
	 * @param data 要加密的数据
	 * @param privateKey 私钥字符串
	 * @param charset 字符串编码类型
	 * @return 返回加密后的结果
	 * @throws RuntimeException
	 *             通过{@link RuntimeException#getCause()}方法可以取得实际的异常原因
	 * 
	 *             <pre>
	 * · {@link InvalidKeyException} 这是无效密钥（无效编码、错误长度、未初始化等）
	 * · {@link NoSuchAlgorithmException} 加密类型在当前环境不可用。通常可以忽略此异常
	 * · {@link NoSuchPaddingException} 填充机制在当前环境不可用。通常可以忽略此异常。
	 * · {@link BadPaddingException} 当期望输入数据的特定填充机制但数据未正确填充时，会抛出此异常。通常是因为密钥错误，不能加密或解密此数据
	 * · {@link IllegalBlockSizeException} 当提供给块密码的数据长度不正确时，抛出该异常，即与密码的块大小不匹配。可以忽略此异常，如果出现则为本类自身代码错误。
	 * · {@link UnsupportedEncodingException} 编码不存在
	 * · {@link InvalidKeySpecException} 私钥信息解析错误
	 *             </pre>
	 */
	public static String privateEncrypt(String data, String privateKey, String charset) {
		return rsaCodec(data, getPrivateKey(privateKey), Cipher.ENCRYPT_MODE, charset);
	}

	/**
	 * 私钥加密字符串数据（默认{@code UTF-8}编码），数据长度最好保持在 keySize/8 以下.
	 * 
	 * @param data 要加密的数据
	 * @param privateKey 私钥字符串
	 * @return 返回加密后的结果
	 * @throws RuntimeException
	 *             通过{@link RuntimeException#getCause()}方法可以取得实际的异常原因
	 * 
	 *             <pre>
	 * · {@link InvalidKeyException} 这是无效密钥（无效编码、错误长度、未初始化等）
	 * · {@link NoSuchAlgorithmException} 加密类型在当前环境不可用。通常可以忽略此异常
	 * · {@link NoSuchPaddingException} 填充机制在当前环境不可用。通常可以忽略此异常。
	 * · {@link BadPaddingException} 当期望输入数据的特定填充机制但数据未正确填充时，会抛出此异常。通常是因为密钥错误，不能加密或解密此数据
	 * · {@link IllegalBlockSizeException} 当提供给块密码的数据长度不正确时，抛出该异常，即与密码的块大小不匹配。可以忽略此异常，如果出现则为本类自身代码错误。
	 * · {@link InvalidKeySpecException} 私钥信息解析错误
	 *             </pre>
	 */
	public static String privateEncrypt(String data, String privateKey) {
		return privateEncrypt(data, privateKey, DEFAULT_CHARSET);
	}

	// ### 解密

	/**
	 * 私钥解密数据，数据长度最好保持在 keySize/8 以下.
	 * 
	 * @param data 要解密的数据
	 * @param privateKey 私钥对象
	 * @return 返回解密后的结果
	 * @throws RuntimeException
	 *             通过{@link RuntimeException#getCause()}方法可以取得实际的异常原因
	 * 
	 *             <pre>
	 * · {@link InvalidKeyException} 这是无效密钥（无效编码、错误长度、未初始化等）
	 * · {@link NoSuchAlgorithmException} 加密类型在当前环境不可用。通常可以忽略此异常
	 * · {@link NoSuchPaddingException} 填充机制在当前环境不可用。通常可以忽略此异常。
	 * · {@link BadPaddingException} 当期望输入数据的特定填充机制但数据未正确填充时，会抛出此异常。通常是因为密钥错误，不能加密或解密此数据
	 * · {@link IllegalBlockSizeException} 当提供给块密码的数据长度不正确时，抛出该异常，即与密码的块大小不匹配。可以忽略此异常，如果出现则为本类自身代码错误。
	 *             </pre>
	 */
	public static byte[] privateDecrypt(byte[] data, RSAPrivateKey privateKey) {
		return rsaCodec(data, privateKey, Cipher.DECRYPT_MODE);
	}

	/**
	 * 私钥解密字符串数据，数据长度最好保持在 keySize/8 以下.
	 * 
	 * @param data 要解密的数据
	 * @param privateKey 私钥对象
	 * @param charset 编码类型
	 * @return 返回解密后的结果
	 * @throws RuntimeException
	 *             通过{@link RuntimeException#getCause()}方法可以取得实际的异常原因
	 * 
	 *             <pre>
	 * · {@link InvalidKeyException} 这是无效密钥（无效编码、错误长度、未初始化等）
	 * · {@link NoSuchAlgorithmException} 加密类型在当前环境不可用。通常可以忽略此异常
	 * · {@link NoSuchPaddingException} 填充机制在当前环境不可用。通常可以忽略此异常。
	 * · {@link BadPaddingException} 当期望输入数据的特定填充机制但数据未正确填充时，会抛出此异常。通常是因为密钥错误，不能加密或解密此数据
	 * · {@link IllegalBlockSizeException} 当提供给块密码的数据长度不正确时，抛出该异常，即与密码的块大小不匹配。可以忽略此异常，如果出现则为本类自身代码错误。
	 * · {@link UnsupportedEncodingException} 编码不存在
	 *             </pre>
	 */
	public static String privateDecrypt(String data, RSAPrivateKey privateKey, String charset) {
		return rsaCodec(data, privateKey, Cipher.DECRYPT_MODE, charset);
	}

	/**
	 * 私钥解密字符串数据(默认{@code UTF-8}编码)，数据长度最好保持在 keySize/8 以下.
	 * 
	 * @param data 要解密的数据
	 * @param privateKey 私钥对象
	 * @return 返回解密后的结果
	 * @throws RuntimeException
	 *             通过{@link RuntimeException#getCause()}方法可以取得实际的异常原因
	 * 
	 *             <pre>
	 * · {@link InvalidKeyException} 这是无效密钥（无效编码、错误长度、未初始化等）
	 * · {@link NoSuchAlgorithmException} 加密类型在当前环境不可用。通常可以忽略此异常
	 * · {@link NoSuchPaddingException} 填充机制在当前环境不可用。通常可以忽略此异常。
	 * · {@link BadPaddingException} 当期望输入数据的特定填充机制但数据未正确填充时，会抛出此异常。通常是因为密钥错误，不能加密或解密此数据
	 * · {@link IllegalBlockSizeException} 当提供给块密码的数据长度不正确时，抛出该异常，即与密码的块大小不匹配。可以忽略此异常，如果出现则为本类自身代码错误。
	 *             </pre>
	 */
	public static String privateDecrypt(String data, RSAPrivateKey privateKey) {
		return privateDecrypt(data, privateKey, DEFAULT_CHARSET);
	}

	/**
	 * 私钥解密数据，数据长度最好保持在 keySize/8 以下.
	 * 
	 * @param data 要解密的数据
	 * @param privateKey 私钥字符串
	 * @return 返回解密后的结果
	 * @throws RuntimeException
	 *             通过{@link RuntimeException#getCause()}方法可以取得实际的异常原因
	 * 
	 *             <pre>
	 * · {@link InvalidKeyException} 这是无效密钥（无效编码、错误长度、未初始化等）
	 * · {@link NoSuchAlgorithmException} 加密类型在当前环境不可用。通常可以忽略此异常
	 * · {@link NoSuchPaddingException} 填充机制在当前环境不可用。通常可以忽略此异常。
	 * · {@link BadPaddingException} 当期望输入数据的特定填充机制但数据未正确填充时，会抛出此异常。通常是因为密钥错误，不能加密或解密此数据
	 * · {@link IllegalBlockSizeException} 当提供给块密码的数据长度不正确时，抛出该异常，即与密码的块大小不匹配。可以忽略此异常，如果出现则为本类自身代码错误。
	 * · {@link InvalidKeySpecException} 私钥信息解析错误
	 *             </pre>
	 */
	public static byte[] privateDecrypt(byte[] data, String privateKey) {
		return rsaCodec(data, getPrivateKey(privateKey), Cipher.DECRYPT_MODE);
	}

	/**
	 * 私钥解密字符串数据，数据长度最好保持在 keySize/8 以下.
	 * 
	 * @param data 要解密的数据
	 * @param privateKey 私钥字符串
	 * @param charset 编码类型
	 * @return 返回解密后的结果
	 * @throws RuntimeException
	 *             通过{@link RuntimeException#getCause()}方法可以取得实际的异常原因
	 * 
	 *             <pre>
	 * · {@link InvalidKeyException} 这是无效密钥（无效编码、错误长度、未初始化等）
	 * · {@link NoSuchAlgorithmException} 加密类型在当前环境不可用。通常可以忽略此异常
	 * · {@link NoSuchPaddingException} 填充机制在当前环境不可用。通常可以忽略此异常。
	 * · {@link BadPaddingException} 当期望输入数据的特定填充机制但数据未正确填充时，会抛出此异常。通常是因为密钥错误，不能加密或解密此数据
	 * · {@link IllegalBlockSizeException} 当提供给块密码的数据长度不正确时，抛出该异常，即与密码的块大小不匹配。可以忽略此异常，如果出现则为本类自身代码错误。
	 * · {@link UnsupportedEncodingException} 编码不存在
	 * · {@link InvalidKeySpecException} 私钥信息解析错误
	 *             </pre>
	 */
	public static String privateDecrypt(String data, String privateKey, String charset) {
		return rsaCodec(data, getPrivateKey(privateKey), Cipher.DECRYPT_MODE, charset);
	}

	/**
	 * 私钥解密字符串数据(默认{@code UTF-8}编码)，数据长度最好保持在 keySize/8 以下.
	 * 
	 * @param data 要解密的数据
	 * @param privateKey 私钥字符串
	 * @return 返回解密后的结果
	 * @throws RuntimeException
	 *             通过{@link RuntimeException#getCause()}方法可以取得实际的异常原因
	 * 
	 *             <pre>
	 * · {@link InvalidKeyException} 这是无效密钥（无效编码、错误长度、未初始化等）
	 * · {@link NoSuchAlgorithmException} 加密类型在当前环境不可用。通常可以忽略此异常
	 * · {@link NoSuchPaddingException} 填充机制在当前环境不可用。通常可以忽略此异常。
	 * · {@link BadPaddingException} 当期望输入数据的特定填充机制但数据未正确填充时，会抛出此异常。通常是因为密钥错误，不能加密或解密此数据
	 * · {@link IllegalBlockSizeException} 当提供给块密码的数据长度不正确时，抛出该异常，即与密码的块大小不匹配。可以忽略此异常，如果出现则为本类自身代码错误。
	 * · {@link InvalidKeySpecException} 私钥信息解析错误
	 *             </pre>
	 */
	public static String privateDecrypt(String data, String privateKey) {
		return privateDecrypt(data, privateKey, DEFAULT_CHARSET);
	}

	/**
	 * 公钥解密数据，数据长度最好保持在 keySize/8 以下.
	 * 
	 * @param data 要解密的数据
	 * @param publicKey 公钥对象
	 * @return 返回解密后的结果
	 * @throws RuntimeException
	 *             通过{@link RuntimeException#getCause()}方法可以取得实际的异常原因
	 * 
	 *             <pre>
	 * · {@link InvalidKeyException} 这是无效密钥（无效编码、错误长度、未初始化等）
	 * · {@link NoSuchAlgorithmException} 加密类型在当前环境不可用。通常可以忽略此异常
	 * · {@link NoSuchPaddingException} 填充机制在当前环境不可用。通常可以忽略此异常。
	 * · {@link BadPaddingException} 当期望输入数据的特定填充机制但数据未正确填充时，会抛出此异常。通常是因为密钥错误，不能加密或解密此数据
	 * · {@link IllegalBlockSizeException} 当提供给块密码的数据长度不正确时，抛出该异常，即与密码的块大小不匹配。可以忽略此异常，如果出现则为本类自身代码错误。
	 *             </pre>
	 */
	public static byte[] publicDecrypt(byte[] data, RSAPublicKey publicKey) {
		return rsaCodec(data, publicKey, Cipher.DECRYPT_MODE);
	}

	/**
	 * 公钥解密字符串数据，数据长度最好保持在 keySize/8 以下.
	 * 
	 * @param data 要解密的数据
	 * @param publicKey 公钥对象
	 * @param charset 编码类型
	 * @return 返回解密后的结果
	 * @throws RuntimeException
	 *             通过{@link RuntimeException#getCause()}方法可以取得实际的异常原因
	 * 
	 *             <pre>
	 * · {@link InvalidKeyException} 这是无效密钥（无效编码、错误长度、未初始化等）
	 * · {@link NoSuchAlgorithmException} 加密类型在当前环境不可用。通常可以忽略此异常
	 * · {@link NoSuchPaddingException} 填充机制在当前环境不可用。通常可以忽略此异常。
	 * · {@link BadPaddingException} 当期望输入数据的特定填充机制但数据未正确填充时，会抛出此异常。通常是因为密钥错误，不能加密或解密此数据
	 * · {@link IllegalBlockSizeException} 当提供给块密码的数据长度不正确时，抛出该异常，即与密码的块大小不匹配。可以忽略此异常，如果出现则为本类自身代码错误。
	 * · {@link UnsupportedEncodingException} 编码不存在
	 *             </pre>
	 */
	public static String publicDecrypt(String data, RSAPublicKey publicKey, String charset) {
		return rsaCodec(data, publicKey, Cipher.DECRYPT_MODE, charset);
	}

	/**
	 * 公钥解密字符串数据(默认{@code UTF-8}编码)，数据长度最好保持在 keySize/8 以下.
	 * 
	 * @param data 要解密的数据
	 * @param publicKey 公钥对象
	 * @return 返回解密后的结果
	 * @throws RuntimeException
	 *             通过{@link RuntimeException#getCause()}方法可以取得实际的异常原因
	 * 
	 *             <pre>
	 * · {@link InvalidKeyException} 这是无效密钥（无效编码、错误长度、未初始化等）
	 * · {@link NoSuchAlgorithmException} 加密类型在当前环境不可用。通常可以忽略此异常
	 * · {@link NoSuchPaddingException} 填充机制在当前环境不可用。通常可以忽略此异常。
	 * · {@link BadPaddingException} 当期望输入数据的特定填充机制但数据未正确填充时，会抛出此异常。通常是因为密钥错误，不能加密或解密此数据
	 * · {@link IllegalBlockSizeException} 当提供给块密码的数据长度不正确时，抛出该异常，即与密码的块大小不匹配。可以忽略此异常，如果出现则为本类自身代码错误。
	 * · {@link UnsupportedEncodingException} 编码不存在
	 *             </pre>
	 */
	public static String publicDecrypt(String data, RSAPublicKey publicKey) {
		return publicDecrypt(data, publicKey, DEFAULT_CHARSET);
	}

	/**
	 * 公钥解密数据，数据长度最好保持在 keySize/8 以下.
	 * 
	 * @param data 要解密的数据
	 * @param publicKey 公钥字符串
	 * @return 返回解密后的结果
	 * @throws RuntimeException
	 *             通过{@link RuntimeException#getCause()}方法可以取得实际的异常原因
	 * 
	 *             <pre>
	 * · {@link InvalidKeyException} 这是无效密钥（无效编码、错误长度、未初始化等）
	 * · {@link NoSuchAlgorithmException} 加密类型在当前环境不可用。通常可以忽略此异常
	 * · {@link NoSuchPaddingException} 填充机制在当前环境不可用。通常可以忽略此异常。
	 * · {@link BadPaddingException} 当期望输入数据的特定填充机制但数据未正确填充时，会抛出此异常。通常是因为密钥错误，不能加密或解密此数据
	 * · {@link IllegalBlockSizeException} 当提供给块密码的数据长度不正确时，抛出该异常，即与密码的块大小不匹配。可以忽略此异常，如果出现则为本类自身代码错误。
	 * · {@link InvalidKeySpecException} 私钥信息解析错误
	 *             </pre>
	 */
	public static byte[] publicDecrypt(byte[] data, String publicKey) {
		return rsaCodec(data, getPublicKey(publicKey), Cipher.DECRYPT_MODE);
	}

	/**
	 * 公钥解密字符串数据，数据长度最好保持在 keySize/8 以下.
	 * 
	 * @param data 要解密的数据
	 * @param publicKey 公钥字符串
	 * @param charset 编码类型
	 * @return 返回解密后的结果
	 * @throws RuntimeException
	 *             通过{@link RuntimeException#getCause()}方法可以取得实际的异常原因
	 * 
	 *             <pre>
	 * · {@link InvalidKeyException} 这是无效密钥（无效编码、错误长度、未初始化等）
	 * · {@link NoSuchAlgorithmException} 加密类型在当前环境不可用。通常可以忽略此异常
	 * · {@link NoSuchPaddingException} 填充机制在当前环境不可用。通常可以忽略此异常。
	 * · {@link BadPaddingException} 当期望输入数据的特定填充机制但数据未正确填充时，会抛出此异常。通常是因为密钥错误，不能加密或解密此数据
	 * · {@link IllegalBlockSizeException} 当提供给块密码的数据长度不正确时，抛出该异常，即与密码的块大小不匹配。可以忽略此异常，如果出现则为本类自身代码错误。
	 * · {@link UnsupportedEncodingException} 编码不存在
	 * · {@link InvalidKeySpecException} 私钥信息解析错误
	 *             </pre>
	 */
	public static String publicDecrypt(String data, String publicKey, String charset) {
		return rsaCodec(data, getPublicKey(publicKey), Cipher.DECRYPT_MODE, charset);
	}

	/**
	 * 公钥解密字符串数据(默认{@code UTF-8}编码)，数据长度最好保持在 keySize/8 以下.
	 * 
	 * @param data 要解密的数据
	 * @param publicKey 公钥字符串
	 * @return 返回解密后的结果
	 * @throws RuntimeException
	 *             通过{@link RuntimeException#getCause()}方法可以取得实际的异常原因
	 * 
	 *             <pre>
	 * · {@link InvalidKeyException} 这是无效密钥（无效编码、错误长度、未初始化等）
	 * · {@link NoSuchAlgorithmException} 加密类型在当前环境不可用。通常可以忽略此异常
	 * · {@link NoSuchPaddingException} 填充机制在当前环境不可用。通常可以忽略此异常。
	 * · {@link BadPaddingException} 当期望输入数据的特定填充机制但数据未正确填充时，会抛出此异常。通常是因为密钥错误，不能加密或解密此数据
	 * · {@link IllegalBlockSizeException} 当提供给块密码的数据长度不正确时，抛出该异常，即与密码的块大小不匹配。可以忽略此异常，如果出现则为本类自身代码错误。
	 * · {@link UnsupportedEncodingException} 编码不存在
	 * · {@link InvalidKeySpecException} 私钥信息解析错误
	 *             </pre>
	 */
	public static String publicDecrypt(String data, String publicKey) {
		return publicDecrypt(data, publicKey, DEFAULT_CHARSET);
	}

	private static String rsaCodec(String data, Key key, int encryptMode, String charset) {
		try {
			boolean isEnctype = encryptMode == Cipher.ENCRYPT_MODE;
			byte[] bData = isEnctype ? data.getBytes(charset) : Base64.getDecoder().decode(data);
			byte[] b = rsaCodec(bData, key, encryptMode);
			return isEnctype ? Base64.getEncoder().encodeToString(b) : new String(b, charset);
		} catch (UnsupportedEncodingException e) {
			throw new RuntimeException(charset + " 编码不存在！", e);
		}
	}

	private static byte[] rsaCodec(byte[] data, Key key, int encryptMode) {
		try {
			Cipher cipher = Cipher.getInstance(ALGORITHM);
			cipher.init(encryptMode, key);
			return rsaSplitCodec(cipher, encryptMode, data, ((RSAKey) key).getModulus().bitLength());
		} catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException | IllegalBlockSizeException
				| BadPaddingException e) {
			String keyType = key instanceof PublicKey ? "公钥" : "私钥";
			String encType = encryptMode == Cipher.ENCRYPT_MODE ? "加密" : "解密";
			throw new RuntimeException(keyType + encType + "出现错误！", e);
		}
	}

	/**
	 * RSA 分割解码.
	 * 加密长度不受限制，但是如果内容过长会导致速度很慢。
	 * 
	 * @param cipher {@link Cipher}类实例化对象
	 * @param opmode
	 *            {@link Cipher#ENCRYPT_MODE}是加密操作，{@link Cipher#DECRYPT_MODE}是解密操作。
	 * @param datas 要进行加密或解密得数据
	 * @param keySize RSA key大小
	 * @return 返回加密或解密后得数据
	 * @throws BadPaddingException 当期望输入数据的特定填充机制但数据未正确填充时，会抛出此异常。
	 * @throws IllegalBlockSizeException 当提供给块密码的数据长度不正确时，抛出该异常，即与密码的块大小不匹配。
	 */
	private static byte[] rsaSplitCodec(Cipher cipher, int opmode, byte[] datas, int keySize)
			throws IllegalBlockSizeException, BadPaddingException {
		int maxBlock = 0;  // 最大块
		if (opmode == Cipher.DECRYPT_MODE) {
			maxBlock = keySize / 8;
		} else {
			maxBlock = keySize / 8 - 11;
		}
		try (ByteArrayOutputStream out = new ByteArrayOutputStream((int) (datas.length * 1.3))) {
			
			int offSet = 0;
			byte[] buff;
			int i = 0;
			while (datas.length > offSet) {
				if (datas.length - offSet > maxBlock) {
					// 可以调用以下的doFinal方法完成加密或解密数据：
					buff = cipher.doFinal(datas, offSet, maxBlock);
				} else {
					buff = cipher.doFinal(datas, offSet, datas.length - offSet);
				}
				out.write(buff, 0, buff.length);
				i ++;
				offSet = i * maxBlock;
			}
			buff = null;
			byte[] resultDatas = out.toByteArray();
			return resultDatas;
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}
}
